// 9.2 容器库概览
/**
 * 一般来说，每个容器都定义在一个头文件中，文件名与类型名相同。即，deque定义在头文件deque中，list定义在头文件list中，以此类推。
 *
 */

#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array;
#include "../Chapter07/Sales_data.h"

// 9.2.1 迭代器
/**
 * 标准库容器的所有迭代器都定义了递增运算符，从当前元素移动到下一个元素。
 * forward_list迭代器不支持递减运算符（--）。
 * 一个迭代器范围（iterator range）由一对迭代器表示，两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。
 * 这两个迭代器通常被称为begin和end.
 */

// 9.2.2 容器类型成员
/**
 * 每个容器都定义了多个类型，如表9.2所示（第295页）。我们已经使用过其中三种：
 * 1. size_type（参见3.2.2节，第79页）
 * 2. iterator
 * 3. const_iterator（参见3.4.1节，第97页）。
 * 反向迭代器就是一种反向遍历容器的迭代器，与正向迭代器相比，各种操作的含义也都发生了颠倒。
 * 例如，对一个反向迭代器执行++操作，会得到上一个元素。我们将在
 */
list<string>::iterator iter;        // iter是通过list<string>定义的一种迭代器类型
vector<int>::difference_type count; // count是通过vector<int>定义的一个difference_type类型
vector<int>::value_type vt;         // 如果需要元素类型，可以使用容器的value_type。
list<string>::reference rf;         // 如果需要元素类型的一个引用，可以使用reference或const_reference
vector<int>::const_reference crf;   //

// 9.2.3 begin和end成员
/**
 * begin和end操作（参见3.4.1节，第95页）生成指向容器中第一个元素和尾元素之后位置的迭代器。
 * 这两个迭代器最常见的用途是形成一个包含容器中所有元素的迭代器范围。
 * 如表9.2（第295页）所示，begin和end有多个版本：
 * 1. 带r的版本返回反向迭代器（我们将在10.4.3节（第363页）中介绍相关内容）；
 * 2. 以c开头的版本则返回const迭代器
 */
list<string> a = {"Milton", "Shakespeare", "Austen"};
auto it1 = a.begin();                         // list<string>::iterator
auto it2 = a.rbegin();                        // list<string>::reverse_iterator
auto it3 = a.cbegin();                        // list<string>::const_iterator
auto it4 = a.crbegin();                       // list<string>::const_reverse_iterator
list<string>::const_iterator it5 = a.begin(); // 过去未引入新标准C++时，只能显示指定类型

// 9.2.4 容器定义和初始化
/**
 * 每个容器类型都定义了一个默认构造函数（参见7.1.4节，第236页）。
 * 将一个新容器创建为另一个容器的拷贝的方法有两种：可以直接拷贝整个容器，或者（array除外）拷贝由一个迭代器对指定的元素范围。
 * 除了与关联容器相同的构造函数外，顺序容器（array除外）还提供另一个构造函数，它接受一个容器大小和一个（可选的）元素初始值。
 * 如果我们不提供元素初始值，则标准库会创建一个值初始化器（参见3.3.1节，第88页）
 * 只有顺序容器的构造函数才接受大小参数，关联容器并不支持。
 * 标准库array具有固定大小。
 */
list<string> authors = {"Milton", "Shakespeare", "Austen"}; // 列表初始化
vector<const char *> articles = {"a", "an", "the"};         // 容器有三个元素，用给定的初始化器进行初始化
list<string> list2(authors);                                // 正确，类型匹配
deque<string> authList(authors);                            // 错误，类型不匹配
vector<string> words(articles);                             // 错误，容器类型必须匹配
// 正确，可将const char*元素转换为string
forward_list<string> words(articles.begin(), articles.end());
vector<int> ivec(10, -1);     // 10个int元素，每个都初始化为-1
list<string> svec(10, "hi!"); // 10个string，每个都初始化为hi!
forward_list<int> ivec(10);   // 10个元素，每个都初始化为0
deque<string> svec(10);       // 10个元素，每个都是空string
// 与内置数组一样，标准库array的大小也是类型的一部分。当定义一个array时，除了指定元素类型，还要指定容器大小：
array<int, 42> arr1;         // 类型为保存42个int的数组，42个默认初始化的int
array<string, 42> arr2;      // 类型为保存42个string的数组
array<int, 10>::size_type i; // 数组类型包括元素类型和大小
array<int>::size_type j;     // 错误，array<int>不是一个类型，缺少大小
array<int, 10> arr3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> arr4 = {10}; // arr4[0]为10，其余都是0
// 值得注意的是，虽然我们不能对内置数组类型进行拷贝或对象赋值操作（参见3.5.1节，第102页），但array并无此限制：
int digs[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};          // 内置数组类型
int cpy[10] = digs;                                     // 错误，内置数组不支持拷贝和赋值
array<int, 10> digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // array数组类型
array<int, 10> copy = digits;                           // 正确，只要数组类型匹配就合法

// 9.2.5 赋值和swap
/**
 * 赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝：
 * c1 = c2; // 将c1的值替换为c2中元素的拷贝
 * c1 = {a,b,c}; // 赋值后，c1的大小为3
 * 由于右边运算对象的大小可能与左边运算对象的大小不同，因此array类型不支持assign，也不允许用花括号包围的值列表进行赋值。
 * 顺序容器（array除外）还定义了一个名为assign的成员，允许我们从一个不同但相容的类型赋值，或者从容器的一个子序列赋值。
 * assign操作用参数所指定的元素（的拷贝）替换左边容器中的所有元素。
 * swap操作交换两个相同类型容器的内容。调用swap之后，两个容器中的元素将会交换。
 * 除string外，指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。
 * 但是，在swap之后，这些元素已经属于不同的容器了。
 * 例如，假定iter在swap之前指向svec1[3]的string，那么在swap之后它指向svec2[3]的元素。
 * 与其他容器不同，对一个string调用swap会导致迭代器、引用和指针失效。
 * 与其他容器不同，swap两个array会真正交换它们的元素。因此，交换两个array所需的时间与array中元素的数目成正比。
 */
array<int, 10> a1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> a2 = {0}; // a2[0]为0，其余元素也为0
list<string> names;
vector<const char *> oldstyle;
names = oldstyle;       // 错误，容器类型不匹配
list<string> slist1(1); // 1个元素，为空string
vector<string> svec1(10);
vector<string> svec2(24);

// 9.2.6 容器大小操作
/**
 * 除了一个例外，每个容器类型都有三个与大小相关的操作。
 * 1. 成员函数size（参见3.2.2节，第78页）返回容器中元素的数目；
 * 2. empty当size为0时返回布尔值true，否则返回false；
 * 2. max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值。
 * forward_list支持max_size和empty，但不支持size。原因将在下一节解释。
 */

// 9.2.7 关系运算符
/**
 * 每个容器类型都支持相等运算符（==和！=）；除了无序关联容器外的所有容器都支持关系运算符（>、>=、<、<=）。
 * 比较两个容器实际上是进行元素的逐对比较。容器的关系运算符使用元素的关系运算符完成比较。
 * 如果元素类型不支持所需运算符，那么保存这种元素的容器就不能使用相应的关系运算。
 * 例如，我们在第7章中定义的Sales_data类型并未定义==和<运算。因此，就不能比较两个保存Sales_data元素的容器.
 */
vector<int> v1 = {1, 3, 5, 7, 9, 12};
vector<int> v2 = {1, 3, 9};
vector<int> v3 = {1, 3, 5, 7};
vector<int> v4 = {1, 3, 5, 7, 9, 12};

int main()
{
    a1 = a2;                                          // 替换a1中的元素
    a1 = {0};                                         // 错误，不能将一个花括号列表赋予数组
    names.assign(oldstyle.cbegin(), oldstyle.cend()); // 正确，可以将const char*转换成string
    slist1.assign(10, "Hiya!");                       // 10个元素，每个都是Hiya!。
    swap(svec1, svec2);                               // 交换两个相同类型容器的内容
    v1 < v2;                                          // true
    v1 < v3;                                          // false
    v1 == v4;                                         // true
    v1 == v2;                                         // false
    vector<Sales_data> storeA, storeB;
    storeA < storeB; // 错误，Sales_data没有<运算符

    return 0;
}